const PENDDING = 'pendding'
const FULLFILLED = 'fullfilled'
const REJECTED = 'rejected'


function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('存在循环引用'))
  }
  let called
  if (typeof x === 'object' && x !== null || typeof x === 'function') {
    try {
      let then = x.then
      if (typeof then === 'function') {
        then.call(x, (y) => {
          if (called) return
          called = true
          resolvePromise(promise2, y, resolve, reject)
        }, (r) => {
          if (called) return
          called = true
          reject(r)
        })
      } else {
        resolve(x)
      }
    } catch (e) {
      if (called) return
      called = true
      reject(e)
    }
  } else {
    // 普通值
    resolve(x)
  }
}

class MyPromise {
  constructor(exec) {
    this.status = PENDDING
    this.value = undefined
    this.reason = undefined
    this.onFullfilledFn = []
    this.onRejectedFn = []
    let resolve = (value) => {
      if (value instanceof MyPromise) {
        return value.then(resolve, reject)
      }
      if (this.status === PENDDING) {
        this.status = FULLFILLED
        this.value = value
        while (this.onFullfilledFn.length > 0) {
          this.onFullfilledFn.shift()(value)
        }
      }
    }
    let reject = (reason) => {
      if (value instanceof Promise) {
        return value.then(resolve, reject)
      }
      if (this.status === PENDDING) {
        this.status = REJECTED
        this.reason = reason
        while (this.onRejectedFn.length > 0) {
          this.onRejectedFn.shift()(reason)
        }
      }
    }
    try {
      exec(resolve, reject)
    } catch (e) {
      reject(e)
    }
  }

  then(onFullfilled, onRejected) {
    onFullfilled = typeof onFullfilled === 'function' ? onFullfilled : v => v
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw (err) }

    let promise2 = new Promise((resolve, reject) => {
      if (this.status === PENDDING) {
        this.onFullfilledFn.push(() => {
          setTimeout(() => {
            try {
              const x = onFullfilled(this.value)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)
        })
        this.onRejectedFn.push(() => {
          setTimeout(() => {
            try {
              const x = onRejected(this.reason)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)
        })
      }
      if (this.status === FULLFILLED) {
        setTimeout(() => {
          try {
            const x = onFullfilled(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      }
      if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            const x = onRejected(this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      }
    })
    return promise2
  }

  static resolve(value) {
    return new Promise((resolve, reject) => {
      resolve(value)
    })
  }
  static reject(reason) {
    return new Promise((resolve, reject) => {
      reject(reason)
    })
  }
}

Promise.prototype.catch = function (cb) {
  return this.then(null, cb)
}

Promise.prototype.finally = function (cb) {
  return this.then((value) => {
    return Promise.resolve(cb).then(() => value)
  }, (reason) => {
    return Promise.resolve(cb).then(() => { throw reason })
  })
}


Promise.prototype.all = function (fns) {
  if (Array.isArray(fns)) {
    return new TypeError('需要传入一个数组')
  }
  return new Promise((resolve, reject) => {
    let results = []
    let finishIndex = 0
    function handleResult(value, i) {
      results[i] = value
      finishIndex++
      if (finishIndex === fns.length) {
        resolve(value)
      }
    }
    for (let i = 0; i < fns.length; i++) {
      let fn = fns[i]
      if (fn && typeof fn.then === 'function') {
        fn.then((value) => {
          handleResult(value, i)
        }, reject)
      } else {
        handleResult(fn, i)
      }
    }
  })
}

Promise.prototype.race = function (promises) {
  return new Promise((resolve, reject) => {
    for (let i = 0; i < promises.length; i++) {
      let promise = promises[i]
      if (promise && typeof promise.then === 'function') {
        promise.then(resolve, reject)
      } else {
        resolve(promise)
      }
    }
  })
}